import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Einstein extends JFrame implements ActionListener{
	
	// Attributes
	protected int size;
	protected Vector<Unit> units;
	protected LinkedHashMap<Object, Unit>unitMap = new LinkedHashMap<Object, Unit>();
	protected LinkedHashSet<Hint> hints = new LinkedHashSet<Hint>();
	protected final int NEXT_TO = 0, LEFT_OF = 1, VERTICAL = 2, BETWEEN = 3;
    
    // Constructor
    public Einstein() {
    	units = new Vector<Unit>();
    }
    
    public Einstein(int size) {
    	this.size = size;
    	units = new Vector<Unit>(size*size);
    }
    
    
    // Units Related Methods
    public void setUnits(Object[] units){
    	this.units.removeAllElements();
    	for(int i=0; i<size*size; i++){
	    	this.units.add(new Unit(this.size, units[i]));
    	}
    	mapUnits();
    }
    
    public void setUnits(char[] units){
    	this.units.removeAllElements();
    	for(int i=0; i<size*size; i++){
	    	this.units.add(new Unit(this.size, new Character(units[i])));
    	}
    	mapUnits();
    }
    
    private void mapUnits(){
    	this.unitMap.clear();
    	for(int i=0; i<units.size(); i++){
    		unitMap.put(units.elementAt(i).getSymbol(), units.elementAt(i));
    	}
    }
    
    public Vector<Unit> getUnits(){
    	return units;
    }
    
    public Unit getUnit(int num){
    	return units.elementAt(num);
    }
    
    public boolean allFound(){
    	boolean result = true;
    	for(int i=0; i<units.size(); i++){
    		result &= units.elementAt(i).isFound();
    	}
    	return result;
    }
    
    public boolean hasEmpty(){
    	boolean result = false;
    	for(int i=0; i<units.size(); i++){
    		result |= units.elementAt(i).isEmpty();
    	}
    	return result;
    }
    
    // Hints Related
    public LinkedHashSet<Hint> getHints(){
    	return hints;
    }
    
    public boolean addHint(int type, Unit[] units){
    	switch(type){
    		case NEXT_TO:
    			if(units[0] != units[1]){
    				hints.add(new NextTo(units));
    				return true;
    			}else{
    				return false;
    			}
    		case LEFT_OF:
    			if(units[0] != units[1]){
    				hints.add(new LeftOf(units));
    				return true;
    			}else{
    				return false;
    			}
    		case VERTICAL:
    			if(units[0] != units[1] && this.units.indexOf(units[0])/size != this.units.indexOf(units[1])/size ){
    				hints.add(new Vertical(units));
    				return true;
    			}else{
    				return false;
    			}
    		case BETWEEN:
    			if(units.length == 3 && units[0] != units[1] && units[0] != units[2] && units[1] != units[2]){
    				hints.add(new Between(units));
    				return true;
    			}else{
    				return false;
    			}
    		default:
    			return false;
    	}
    }
    
    public boolean loadHints(Object[] hintArr){
    	Unit[] tmp = new Unit[3];
    	for(int i=0;i<hintArr.length; i+=3){
    		if(unitMap.get(hintArr[i+1]) != null){	// Between
    			tmp[0] = unitMap.get(hintArr[i]);
    			tmp[1] = unitMap.get(hintArr[i+1]);
    			tmp[2] = unitMap.get(hintArr[i+2]);
    			addHint(BETWEEN, tmp);
    		}else if(unitMap.get(hintArr[i]) != null && unitMap.get(hintArr[i+2]) != null){
    			tmp[0] = unitMap.get(hintArr[i]);
    			tmp[1] = unitMap.get(hintArr[i+2]);
    			if(hintArr[i+1].equals("^")){		// NextTo
    				addHint(NEXT_TO, tmp);
    			}else if(hintArr[i+1].equals(">")){	// LeftOf
    				addHint(LEFT_OF, tmp);
    			}else if(hintArr[i+1].equals("|")){	// Vertical
    				addHint(VERTICAL, tmp);
    			}
    		}else{
    			System.out.println("Hint fail : "+hintArr[0]+hintArr[1]+hintArr[2]);
    		}
    	}
    	return true;
    }
    
	public void printUnitGrid(){
		String str = "";
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++){
				str += units.elementAt(i*size+j)+"\t";
			}
			System.out.println(str);
			str = "";
		}
	}
	
	public void walkHints(){
		for(Hint i : hints){
			// TODO: Check affected rows
			checkAllRows();
			i.applyHint();
		}
	}
	
	public void checkRow(int row){
		// TODO: Optimize algorithm
		BitSet tmp = new BitSet(size);
		boolean changed = false;
		int[] sum = new int[size];
		row *= size;
		do{
			changed = false;
			// Check if is found
			for(int i=0; i<size; i++){
				if(units.elementAt(row+i).isFound()){
					for(int j=0; j<size; j++){
						if(j!=i){
							if(units.elementAt(row+j).rAndNot(units.elementAt(row+i))){
								changed = true;
							}
						}
					}
				}
			}
			
			// Check if only 1 possible unit
			for(int i=0; i<size; i++){
				for(int j=0; j<size; j++){
					sum[i] += ( (units.elementAt(row+j).get(i))? 1 : 0 );
				}
			}
			
			for(int i=0; i<size; i++){
				if(sum[i] == 1){
					tmp.clear();
					tmp.set(i);
					for(int j=0; j<size; j++){
						if(units.elementAt(row+j).get(i)){
							if(!units.elementAt(row+j).isFound()){
								units.elementAt(row+j).clear();
								units.elementAt(row+j).set(i);
								changed = true;
							}
						}else{
							if(units.elementAt(row+j).rAndNot(tmp)){
								changed = true;
							}
						}
					}
				}
			}
		}while(changed);

	}
	
	public void checkAllRows(){
		for(int i=0; i<size; i++){
			checkRow(i);
		}
	}
	
    // Action Listeners
    public void actionPerformed(ActionEvent ae){
    }
    
}